//
//  CloverPrefpane.m
//  CloverPrefpane
//
//  Created by JrCs on 03/05/13.
//  Copyright (c) 2013 ProjectOSX. All rights reserved.
//

#import "CloverPrefpane.h"
#include <mach/mach_error.h>

#define kCloverInstaller @"com.projectosx.clover.installer"

// Global Variables
static const CFStringRef agentIdentifier=CFSTR("com.projectosx.Clover.Updater");
static const CFStringRef agentExecutable=CFSTR("/Library/Application Support/Clover/CloverUpdaterUtility");
static const CFStringRef checkIntervalKey=CFSTR("ScheduledCheckInterval");
static const CFStringRef lastCheckTimestampKey=CFSTR("LastCheckTimestamp");
static const CFStringRef efiDirPathKey=CFSTR("EFI Directory Path");

static NSString* kLogLineCount = @"LogLineCount";
static NSString* kLogEveryBoot = @"LogEveryBoot";
static NSString* kBackupDirOnDestVol = @"BackupDirOnDestVol";
static NSString* kKeepBackupLimit = @"KeepBackupLimit";
static NSString* kMountEFI = @"MountEFI";
static NSString* kNVRamDisk = @"NVRamDisk";

@implementation CloverPrefpane

@synthesize cloverLogEveryBootEnabled; // = _cloverLogEveryBootEnabled;
@synthesize cloverLogEveryBootLimit; //   = _cloverLogEveryBootLimit;

@synthesize diskutilList; //   = _diskutilList;
@synthesize efiPartitions; //   = _efiPartitions;
@synthesize nvRamPartitions; // = _nvRamPartitions;

@synthesize nvram; // = _nvram;
@synthesize themeInfo; // = _themeInfo;

#pragma mark Properties

- (NSDictionary *)diskutilList {
  if (diskutilList == nil) {
    // Get diskutil list -plist output
    NSTask *task = [[NSTask alloc] init];
    
    [task setLaunchPath: @"/usr/sbin/diskutil"];
    [task setArguments:[NSArray arrayWithObjects: @"list", @"-plist", nil]];
    
    NSPipe *pipe = [NSPipe pipe];
    
    [task setStandardOutput: pipe];
    
    NSFileHandle *file = [pipe fileHandleForReading];
    
    [task launch];
    
    NSData *data = [file readDataToEndOfFile];
    
    diskutilList = CFBridgingRelease(CFPropertyListCreateWithData(kCFAllocatorDefault,
                                                                  (__bridge CFDataRef)(data),
                                                                  kCFPropertyListImmutable,
                                                                  NULL, NULL));
  }
  return diskutilList;
}

// Hack to avoid warning generated by genstrings
#define GetLocalized\
String(key, comment)    NSLocalized\
StringFromTableInBundle(key, nil, self.bundle, comment)

#define AddKeyValueToList(list, title, value) \
[list addObject:[NSDictionary dictionaryWithObjectsAndKeys: \
(title), @"Title", \
(value ? value : [NSNull null]), @"Value", nil]]

- (NSArray*)efiPartitions {
  if (efiPartitions == nil) {
    NSMutableArray *list = [[NSMutableArray alloc] init];
    
    AddKeyValueToList(list,
                      GetLocalizedString(@"No",@"Not mounting EFI partition"),
                      nil);
    AddKeyValueToList(list,
                      GetLocalizedString(@"Boot Volume",@"EFI partition from boot volume"),
                      [NSNumber numberWithBool:YES]);
    
    NSArray *disksAndPartitions = [[self diskutilList] objectForKey:@"AllDisksAndPartitions"];
    if (disksAndPartitions != nil) {
      for (NSDictionary *diskEntry in disksAndPartitions) {
        
        NSString *content = [diskEntry objectForKey:@"Content"];
        
        if (content != nil) {
          // Disk has partitions
          if ([content isEqualToString:@"GUID_partition_scheme"] ||
              [content isEqualToString:@"FDisk_partition_scheme"]) {
            
            NSString *diskIdentifier = [diskEntry objectForKey:@"DeviceIdentifier"];
            NSArray *partitions = [diskEntry objectForKey:@"Partitions"];
            
            if (diskIdentifier != nil && partitions != nil) {
              NSMutableArray *volumeNames = [[NSMutableArray alloc] init];
              NSString *espIdentifier = nil;
              
              for (NSDictionary *partitionEntry in partitions) {
                
                NSString *content = [partitionEntry objectForKey:@"Content"];
                
                if (content != nil && [content isEqualToString:@"EFI"]) {
                  
                  NSString *identifier = [partitionEntry objectForKey:@"DeviceIdentifier"];
                  
                  if (identifier != nil) {
                    espIdentifier = [[self getPartitionProperties:identifier] objectForKey:@"UUID"];
                    
                    if (!espIdentifier) {
                      espIdentifier = identifier;
                    }
                  }
                }
                
                NSString *volumeName = [partitionEntry objectForKey:@"VolumeName"];
                
                if (volumeName) {
                  [volumeNames addObject:volumeName];
                }
              }
              
              if (espIdentifier) {
                NSString *name = [NSString stringWithFormat:GetLocalizedString(@"ESP on [%@] %@", nil),
                                  diskIdentifier, [volumeNames componentsJoinedByString:@", "]];
                AddKeyValueToList(list, name, espIdentifier);
              }
            }
          }
        }
      }
    }
    
    efiPartitions = [NSArray arrayWithArray:list];
  }
  return efiPartitions;
}

- (NSArray*)nvRamPartitions {
  if (nil == nvRamPartitions) {
    NSMutableArray *list = [[NSMutableArray alloc] init];
    AddKeyValueToList(list, GetLocalizedString(@"Never",nil),  [NSNumber numberWithBool:NO]);
    AddKeyValueToList(list, GetLocalizedString(@"Auto",nil),   [NSNull null]);
    AddKeyValueToList(list, GetLocalizedString(@"Always",nil), [NSNumber numberWithBool:YES]);
    
    nvRamPartitions = [NSArray arrayWithArray:list];
  }
  
  return nvRamPartitions;
}

- (IBAction)updateCloverLogEveryBoot:(id)sender {
  NSString* logEveryBoot;
  if ([self.cloverLogEveryBootEnabled boolValue] == YES) {
    unsigned int logEveryBootLimit = [self.cloverLogEveryBootLimit unsignedIntValue];
    if (logEveryBootLimit > 0) {
      logEveryBoot = [NSString stringWithFormat:@"%d", logEveryBootLimit];
    } else {
      logEveryBoot=@"Yes";
    }
  }
  else
  logEveryBoot=@"";
  
  [self.nvram setValue:logEveryBoot forKey:kLogEveryBoot];
  [self updateCloverNVRamVariables:nil];
}

- (IBAction)updateCloverNVRamVariables:(id)sender {
  Class boolClass = [[NSNumber numberWithBool:YES] class];
  Class nullClass = [NSNull class];
  
  for (id key in [self.nvram allKeys]) {
    id value = [self.nvram valueForKey:key];
    if ([value isKindOfClass:boolClass]) {
      value = [value  boolValue] ? @"Yes" : @"No";
    } else if ([value isKindOfClass:nullClass]) {
      value = @"";
    } else {
      value = [NSString stringWithFormat:@"%@",value];
    }
    [self setNVRamKey:[NSString stringWithFormat:@"Clover.%@", key] Value:value];
  }
}

#pragma mark Methods

- (NSDictionary*)getPartitionProperties:(NSString*)bsdName {
  CFMutableDictionaryRef  matchingDict;
  io_service_t  service;
  NSDictionary  *result = nil;
  
  matchingDict = IOBSDNameMatching(kIOMasterPortDefault, 0, [bsdName UTF8String]);
  
  if (matchingDict == NULL) {
    NSLog(@"IOBSDNameMatching returned a NULL dictionary");
  } else {
    // Fetch the object with the matching BSD node name.
    // Note that there should only be one match, so IOServiceGetMatchingService is used instead of
    // IOServiceGetMatchingServices to simplify the code.
    service = IOServiceGetMatchingService(kIOMasterPortDefault, matchingDict);
    
    if (IO_OBJECT_NULL == service) {
      NSLog(@"IOServiceGetMatchingService returned IO_OBJECT_NULL");
    } else {
      if (IOObjectConformsTo(service, "IOMedia")) {
        CFMutableDictionaryRef properties;
        IORegistryEntryCreateCFProperties(service,&properties, kCFAllocatorDefault, 0);
        result = (__bridge_transfer id)properties;
      }
      IOObjectRelease(service);
    }
  }
  
  return result;
}


#pragma mark Events

// System Preferences calls this method when the pane is initialized.
- (id)initWithBundle:(NSBundle *)bundle {
  if ( ( self = [super initWithBundle:bundle] ) != nil ) {
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
    NSString *agentsFolder = [[searchPaths objectAtIndex:0] stringByAppendingPathComponent:@"LaunchAgents"];
    
    [[NSFileManager defaultManager]
     createDirectoryAtPath:agentsFolder
     withIntermediateDirectories:YES
     attributes:nil
     error:nil];
    
    agentPlistPath = [[NSString alloc]
                      initWithString:[[agentsFolder stringByAppendingPathComponent:(NSString *)CFBridgingRelease(agentIdentifier)]
                                      stringByAppendingPathExtension:@"plist"]];
    
    // Allocate object for accessing IORegistry
    mach_port_t   masterPort;
    kern_return_t result = IOMasterPort(bootstrap_port, &masterPort);
    if (result != KERN_SUCCESS) {
      NSLog(@"Error getting the IOMaster port: %s", mach_error_string(result));
      exit(1);
    }
    
    _ioRegEntryRef = IORegistryEntryFromPath(masterPort, "IODeviceTree:/options");
    if (_ioRegEntryRef == 0) {
      NSLog(@"nvram is not supported on this system");
    }
    
    diskutilList = nil;
    
    // Init NVRam variables fields
    [self initNVRamVariableFields];
    
  }
  return self;
}

- (void)mainViewDidLoad {
  BOOL plistExists;
  
  // Initialize revision fields
  NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSLocalDomainMask, YES);
  NSString *preferenceFolder = [[searchPaths objectAtIndex:0] stringByAppendingPathComponent:@"Preferences"];
  NSString *cloverInstallerPlist = [[preferenceFolder stringByAppendingPathComponent:kCloverInstaller] stringByAppendingPathExtension:@"plist"];
  plistExists = [[NSFileManager defaultManager] fileExistsAtPath:cloverInstallerPlist];
  NSString* installedRevision = @"-";
  if (plistExists) {
    NSDictionary *dict = [[NSDictionary alloc]
                          initWithContentsOfFile:cloverInstallerPlist];
    NSNumber* revision = [dict objectForKey:@"CloverRevision"];
    if (revision)
    installedRevision = [revision stringValue];
  }
  [lastInstalledRevision setStringValue:installedRevision];
  
  NSString* bootedRevision = @"-";
  io_registry_entry_t ioRegistryEFI = IORegistryEntryFromPath(kIOMasterPortDefault,
                                                              "IODeviceTree:/efi/platform");
  if (ioRegistryEFI) {
    CFStringRef nameRef = CFStringCreateWithCString(kCFAllocatorDefault,
                                                    "clovergui-revision",
                                                    kCFStringEncodingUTF8);
    if (nameRef) {
      CFTypeRef valueRef = IORegistryEntryCreateCFProperty(ioRegistryEFI, nameRef, 0, 0);
      CFRelease(nameRef);
      if (valueRef) {
        // Get the OF variable's type.
        CFTypeID typeID = CFGetTypeID(valueRef);
        if (typeID == CFDataGetTypeID())
        bootedRevision = [NSString stringWithFormat:@"%u",*((uint32_t*)CFDataGetBytePtr(valueRef))];
        CFRelease(valueRef);
      }
    }
    IOObjectRelease(ioRegistryEFI);
  }
  [lastBootedRevision setStringValue:bootedRevision];
  
  // Initialize popUpCheckInterval
  unsigned int checkInterval =
  [self getUIntPreferenceKey:checkIntervalKey forAppID:agentIdentifier withDefault:0];
  [popUpCheckInterval selectItemWithTag:checkInterval];
  
  // Initialize LastRunDate
  unsigned int lastCheckTimestamp =
  [self getUIntPreferenceKey:lastCheckTimestampKey forAppID:agentIdentifier withDefault:0];
  if (lastCheckTimestamp == 0) {
    [LastRunDate setStringValue:@"-"];
  } else {
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:lastCheckTimestamp];
    [LastRunDate setStringValue:[LastRunDate.formatter stringFromDate:date]];
    
  }
  // Enable the checkNowButton if executable is present
  [checkNowButton setEnabled:
   [[NSFileManager defaultManager] fileExistsAtPath:(NSString*)CFBridgingRelease(agentExecutable)]];
  
  // Get EFI Path
  NSString* efiDir=[self getStringPreferenceKey:efiDirPathKey
                                       forAppID:(CFStringRef)[self.bundle bundleIdentifier]
                                    withDefault:CFSTR("/")];
  [_EFIPathControl setURL:[NSURL fileURLWithPath:efiDir]];
  [self initThemeTab:efiDir];
  
  // Setup security.
  AuthorizationItem items = {kAuthorizationRightExecute, 0, NULL, 0};
  AuthorizationRights rights = {1, &items};
  [authView setAuthorizationRights:&rights];
  authView.delegate = self;
  [authView updateStatus:nil];
  
  plistExists = [[NSFileManager defaultManager] fileExistsAtPath:agentPlistPath];
  if (plistExists && checkInterval == 0) {
    [[NSFileManager defaultManager] removeItemAtPath:agentPlistPath error:nil];
  }
}


- (void) initNVRamVariableFields {
  NSString *value;
  self.nvram = [NSMutableDictionary dictionaryWithCapacity:16]; // 16 entries for the moment
  
  value = [self getCloverNVRam:kLogLineCount];
  [self.nvram setValue:value forKey:kLogLineCount];
  
  value = [self getCloverNVRam:kLogEveryBoot];
  [self.nvram setValue:value
                forKey:kLogEveryBoot];
  
  if ([value length] == 0) {
    self.cloverLogEveryBootEnabled = [NSNumber numberWithBool:NO];
    self.cloverLogEveryBootLimit   = [NSNumber numberWithInt:0];
  } else {
    self.cloverLogEveryBootEnabled = [value boolValue] ?
    [NSNumber numberWithBool:YES] :
    [NSNumber numberWithBool:NO];
    self.cloverLogEveryBootLimit   = [NSNumber numberWithInteger:[value integerValue]];
  }
  
  value = [self getCloverNVRam:kBackupDirOnDestVol];
  [self.nvram setValue:value
                forKey:kBackupDirOnDestVol];
  
  value = [self getCloverNVRam:kKeepBackupLimit];
  if ([value length] == 0)
  value = @"0";
  [self.nvram setValue:value
                forKey:kKeepBackupLimit];
  
  value = [self getCloverNVRam:kMountEFI];
  [self.nvram setValue:value forKey:kMountEFI];
  
  value = [self getCloverNVRam:kNVRamDisk];
  [self.nvram setValue:value forKey:kNVRamDisk];
}


#pragma mark -
#pragma mark General Tab Methods
- (IBAction) configureAutomaticUpdates:(id)sender {
  CFDictionaryRef launchInfo = SMJobCopyDictionary(kSMDomainUserLaunchd, agentIdentifier);
  if (launchInfo != NULL) {
    CFRelease(launchInfo);
    CFErrorRef error = NULL;
    if (!SMJobRemove(kSMDomainUserLaunchd, agentIdentifier, NULL, YES, &error))
    NSLog(@"Error in SMJobRemove: %@", error);
    if (error)
    CFRelease(error);
  }
  
  NSInteger checkInterval = [sender tag];
  [self setPreferenceKey:checkIntervalKey forAppID:agentIdentifier fromInt:(int)checkInterval];
  
  
  if (checkInterval > 0 && [[NSFileManager defaultManager] fileExistsAtPath:(NSString*)CFBridgingRelease(agentExecutable)]) {
    // Create a new plist
    NSArray* call = [NSArray arrayWithObjects:
                     (NSString *)CFBridgingRelease(agentExecutable),
                     @"startup",
                     nil];
    NSDictionary *plist = [NSDictionary dictionaryWithObjectsAndKeys:
                           (NSString *)CFBridgingRelease(agentIdentifier), @"Label",
                           [NSNumber numberWithInteger:checkInterval], @"StartInterval",
                           [NSNumber numberWithBool:YES], @"RunAtLoad",
                           (NSString *)CFBridgingRelease(agentExecutable), @"Program",
                           call, @"ProgramArguments",
                           nil];
    [plist writeToFile:agentPlistPath atomically:YES];
    
    CFErrorRef error = NULL;
    if (!SMJobSubmit(kSMDomainUserLaunchd, (__bridge CFDictionaryRef)(plist), NULL, &error)) {
      if (error) {
        NSLog(@"Error in SMJobSubmit: %@", error);
      } else
      NSLog(@"Error in SMJobSubmit without details. Check /var/db/launchd.db/com.apple.launchd.peruser.NNN/overrides.plist for %@ set to disabled.", agentIdentifier);
    }
    if (error)
    CFRelease(error);
  } else {
    // Remove the plist
    [[NSFileManager defaultManager] removeItemAtPath:agentPlistPath error:nil];
  }
  CFPreferencesAppSynchronize(agentIdentifier); // Force the preferences to be save to disk
}

- (IBAction)checkNow:(id)sender {
  [[NSWorkspace sharedWorkspace] launchApplication:(NSString*)CFBridgingRelease(agentExecutable)];
}


- (BOOL)isUnlocked {
  return [authView authorizationState] == SFAuthorizationViewUnlockedState;
}

#pragma mark -
#pragma mark Theme Tab Methods

- (void) initThemeTab:(NSString*) efiDir {
  
  NSFileManager *fileManager = [NSFileManager defaultManager];
  BOOL isDir = NO;
  
  [_cloverThemeComboBox removeAllItems]; // Remove all theme names if exists
  
  NSString *themesDir = [efiDir stringByAppendingPathComponent:@"CLOVER/Themes"];
  [fileManager fileExistsAtPath:themesDir isDirectory:(&isDir)];
  [_themeWarning setHidden:isDir];
  
  // get the list of all files and directories
  NSMutableArray *themes = [[NSMutableArray alloc] init];
  
  NSArray *fileList = [fileManager contentsOfDirectoryAtPath:themesDir error:nil];
  if (fileList) {
    for(NSString *file in fileList) {
      NSString *path = [[themesDir stringByAppendingPathComponent:file]
                        stringByAppendingPathComponent:@"theme.plist"];
      if ([fileManager fileExistsAtPath:path]) {
        [themes addObject:file];
      }
    }
  }
  else {
    // Try to get installed themes
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSLocalDomainMask, YES);
    NSString *preferenceFolder = [[searchPaths objectAtIndex:0] stringByAppendingPathComponent:@"Preferences"];
    NSString *cloverInstallerPlist = [[preferenceFolder stringByAppendingPathComponent:kCloverInstaller] stringByAppendingPathExtension:@"plist"];
    NSDictionary *dict = [[NSDictionary alloc]
                          initWithContentsOfFile:cloverInstallerPlist];
    if (dict) {
      NSArray *installedThemes = [dict objectForKey:@"InstalledThemes"];
      [themes addObjectsFromArray:installedThemes];
    }
    else {
      // Get default themes from bundle
      NSString* defaultThemePlistPath = [self.bundle pathForResource:@"DefaultThemes.plist" ofType:@"plist"];
      NSDictionary *dict = [[NSDictionary alloc]
                            initWithContentsOfFile:defaultThemePlistPath];
      if (dict) {
        NSArray *defaultThemes = [dict objectForKey:@"Default Themes"];
        if (defaultThemes)
        [themes addObjectsFromArray:defaultThemes];
      }
    }
  }
  
  NSString* currentTheme = [self getNVRamKey:@"Clover.Theme"];
  if (currentTheme && [themes indexOfObject:currentTheme] == NSNotFound)
  [themes addObject:currentTheme];
  
  NSArray *sortedThemes = [themes sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
  [_cloverThemeComboBox addItemsWithObjectValues:sortedThemes];
  if (currentTheme) {
    [_cloverThemeComboBox selectItemWithObjectValue:currentTheme];
    [self updateThemeTab:currentTheme];
  }
}

- (void) updateThemeTab:(NSString*) themeName {
  NSString *efiDir    = [[_EFIPathControl URL] path];
  NSString *themeDir  = [[efiDir stringByAppendingPathComponent:@"CLOVER/Themes"]
                         stringByAppendingPathComponent:themeName];
  // Load the theme.plist file
  NSString *themePlistPath = [themeDir stringByAppendingPathComponent:@"theme.plist"];
  
  NSMutableDictionary *newThemeInfo = [NSMutableDictionary dictionaryWithContentsOfFile:themePlistPath];
  if (newThemeInfo) {
    self.themeInfo = newThemeInfo;
  } else {
    self.themeInfo = [NSMutableDictionary dictionaryWithCapacity:16]; // 16 entries for the moment
  }
  
  NSString *imagePath = [themeDir stringByAppendingPathComponent:@"screenshot.png"];
  NSNumber *previewAvailable = [NSNumber numberWithBool:YES];
  if (![[NSFileManager defaultManager] fileExistsAtPath:imagePath]) {
    imagePath = [self.bundle pathForResource:@"NoPreview" ofType:@"png"];
    previewAvailable = [NSNumber numberWithBool:NO];
  }
  [self.themeInfo setObject:previewAvailable forKey:@"PreviewAvailable"];
  
  NSImage* preview = [[NSImage alloc] initWithContentsOfFile:imagePath];
  [self.themeInfo setObject:preview forKey:@"Preview"];
  
  if (![self.themeInfo objectForKey:@"Author"]) {
    [self.themeInfo setObject:GetLocalizedString(@"Unknown",nil) forKey:@"Author"];
  }
  
  if (![self.themeInfo objectForKey:@"Year"]) {
    [self.themeInfo setObject:GetLocalizedString(@"Unknown",nil) forKey:@"Year"];
  }
  
  if (![self.themeInfo objectForKey:@"Description"]) {
    [self.themeInfo setObject:GetLocalizedString(@"No description available",nil) forKey:@"Description"];
  }
  
  // Update NVRam
  NSString *oldValue = [self getNVRamKey:@"Clover.Theme"];
  if ((oldValue.length != 0 || themeName.length != 0) &&
      ![oldValue isEqualToString:themeName]) {
    if ([self setNVRamKey:@"Clover.Theme" Value:themeName] != 0) {
      [_cloverThemeComboBox setStringValue:oldValue];
      [self updateThemeTab:oldValue];
    }
  }
}

- (IBAction)showPathOpenPanel:(id)sender {
  NSOpenPanel *panel = [NSOpenPanel openPanel];
  [panel setAllowsMultipleSelection:NO];
  [panel setCanChooseDirectories:YES];
  [panel setCanChooseFiles:NO];
  [panel setResolvesAliases:YES];
  
  NSString *panelTitle = GetLocalizedString(@"Select the EFI directory", @"Title for the open panel");
  [panel setTitle:panelTitle];
  
  NSString *promptString = GetLocalizedString(@"Set EFI directory", @"Prompt for the open panel prompt");
  [panel setPrompt:promptString];
  
  [panel beginSheetModalForWindow:[sender window] completionHandler:^(NSInteger result) {
    
    // Hide the open panel.
    [panel orderOut:self];
    
    // If the return code wasn't OK, don't do anything.
    if (result != NSOKButton) {
      return;
    }
    // Get the first URL returned from the Open Panel and set it at the first path component of the control.
    NSURL *url = [[panel URLs] objectAtIndex:0];
    [self->_EFIPathControl setURL:url];
    
    NSString *efiDir = [url path];
    [self setPreferenceKey:efiDirPathKey
                  forAppID:(CFStringRef)[self.bundle bundleIdentifier]
                fromString:(CFStringRef)efiDir];
    CFPreferencesAppSynchronize((CFStringRef)[self.bundle bundleIdentifier]); // Force the preferences to be save to disk
    
    [self initThemeTab:efiDir];
  }];
}

- (IBAction)themeComboBox:(NSComboBox*)sender {
  NSString *themeName = [sender stringValue];
  [self updateThemeTab:themeName];
}

#pragma mark -
#pragma mark Authorization delegates

//
// SFAuthorization delegates
//
- (void)authorizationViewDidAuthorize:(SFAuthorizationView *)view {
}

- (void)authorizationViewDidDeauthorize:(SFAuthorizationView *)view {
}


//
// NVRAM methods
//
#pragma mark -
#pragma mark NVRam methods

// Get Clover NVRAM variable
-(NSString*) getCloverNVRam:(const NSString*)key {
  return [self getNVRamKey:[NSString stringWithFormat:@"Clover.%@",key]];
}

// Get NVRAM value
-(NSString*) getNVRamKey:(const NSString*)key {
  NSString*   result = @"";
  
  CFTypeRef valueRef = IORegistryEntryCreateCFProperty(_ioRegEntryRef, (__bridge CFStringRef)(key), 0, 0);
  if (valueRef == 0) {
    return result;
  }
  
  // Get the OF variable's type.
  CFTypeID typeID = CFGetTypeID(valueRef);
  
  if (typeID == CFDataGetTypeID()) {
    result = [NSString stringWithUTF8String:(const char*)CFDataGetBytePtr(valueRef)];
  }
  
  CFRelease(valueRef);
  
  return result;
}

// Set NVRAM key/value pair
- (OSErr) setNVRamKey:(const NSString*)key Value:(NSString*)value {
  
  OSErr processError = 0;
  
  if (key) {
    if (!value) {
      value=@"";
    }
    NSString *oldValue = [self getNVRamKey:key];
    if ((oldValue.length != 0 || value.length != 0) &&
        ![oldValue isEqualToString:value]) {
      
      // Size for key=value + null terminal char
      size_t len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1
      + [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
      char* nvram_arg=(char*) malloc(sizeof(char) * len);
      snprintf(nvram_arg, len, "%s=%s", [key UTF8String], [value UTF8String]);
      
      // Need 2 parameters: key=value and NULL
      const char **argv = (const char **)malloc(sizeof(char *) * 2);
      argv[0] = nvram_arg;
      argv[1] = NULL;
      
      processError = AuthorizationExecuteWithPrivileges([[authView authorization] authorizationRef],
                                                        [@"/usr/sbin/nvram" UTF8String],
                                                        kAuthorizationFlagDefaults,
                                                        (char *const *)argv,
                                                        nil);
      
      if (processError != errAuthorizationSuccess)
      NSLog(@"Error trying to set nvram %s:%d", nvram_arg, processError);
      
      free(argv);
      free(nvram_arg);
    }
  }
  return processError;
}

//
// Preferences methods
//
#pragma mark -
#pragma mark Preferences methods

// get and set preference keys functions idea taken from:
// http://svn.perian.org/branches/perian-1.1/CPFPerianPrefPaneController.m
- (unsigned int)getUIntPreferenceKey:(CFStringRef)key
                            forAppID:(CFStringRef)appID
                         withDefault:(unsigned int)defaultValue {
  CFPropertyListRef value;
  unsigned int ret = defaultValue;
  
  value = CFPreferencesCopyAppValue(key, appID);
  if (value && CFGetTypeID(value) == CFNumberGetTypeID())
  CFNumberGetValue(value, kCFNumberIntType, &ret);
  
  if (value) {
    CFRelease(value);
  }
  
  return ret;
}

- (void)setPreferenceKey:(CFStringRef)key
                forAppID:(CFStringRef)appID
                 fromInt:(int)value {
  CFNumberRef numRef = CFNumberCreate(NULL, kCFNumberIntType, &value);
  CFPreferencesSetAppValue(key, numRef, appID);
  CFRelease(numRef);
}

- (NSString *)getStringPreferenceKey:(CFStringRef)key
                            forAppID:(CFStringRef)appID
                         withDefault:(CFStringRef)defaultValue {
  CFPropertyListRef value;
  NSString *ret = nil;
  
  value = CFPreferencesCopyAppValue(key, appID);
  if(value && CFGetTypeID(value) == CFStringGetTypeID())
  ret = [NSString stringWithString:(__bridge NSString * _Nonnull)(value)];
  else
  ret = [NSString stringWithString:(__bridge NSString * _Nonnull)(defaultValue)];
  
  if(value) {
    CFRelease(value);
  }
  
  return ret;
}

- (void)setPreferenceKey:(CFStringRef)key
                forAppID:(CFStringRef)appID
              fromString:(CFStringRef)value {
  CFPreferencesSetAppValue(key, value, appID);
}
@end
